More Flask patterns, then sketch out the two features you'll build this week: AI generation and search
Day 31 of 80
The Prompt Vault web app will do something genuinely useful: you type a shot description in the browser, click "Generate with Claude", and a production-ready AI video prompt appears on the page — written by Claude, ready to save to your JSON file. You'll also add a search bar so you can find any prompt by keyword.
Today is the preparation session: more Flask video content to fill in your mental model, then a planning exercise to design both features before you write a single line.
Package structure, user authentication patterns, template inheritance (~75 min total) — type along
Parts 5–7 cover user accounts, password hashing, and SQLAlchemy models — none of which you'll use in the Prompt Vault. But don't skip them entirely. The template inheritance pattern ({% extends "base.html" %} and {% block content %}), error handlers (@app.errorhandler(404)), and url_for() are directly applicable to your project. Watch for those and type along with those sections.
| Concept | Why It Matters for Your Project |
|---|---|
url_for("function_name") |
Generates URLs from function names — better than hardcoding "/add" everywhere. If you rename a route, url_for() updates automatically. |
| Template inheritance | {% extends "base.html" %} lets you define a shared layout once. When you add more pages (a detail view, a generate page), they all inherit the same header and nav. |
| Error handlers | @app.errorhandler(404) — catch bad URLs gracefully. A 404 on /delete/999 when there are only 5 prompts should show a friendly message, not a crash. |
| Flash messages | flash("Prompt added!") — a built-in way to show one-time status messages after a redirect. Useful for "Saved!" and "Deleted!" confirmations. |
A new form lets you type a shot description. When you submit it, Python calls Claude with a system prompt, receives the generated text, and passes it back to the same template. The generated prompt appears on the page with a "Save This" button. Clicking Save adds it to prompts.json.
Here's the skeleton — you'll fill this in on Day 32:
@app.route("/generate", methods=["POST"])
def generate():
shot = request.form.get("shot", "").strip()
platform = request.form.get("platform", "Kling")
if not shot:
return redirect("/")
# TODO: Call Claude here
# message = claude.messages.create(...)
# generated = message.content[0].text
# TODO: Pass generated back to template
prompts = load_prompts()
return render_template("index.html",
prompts=prompts,
generated="...",
gen_shot=shot,
gen_platform=platform
)
gen_shot and gen_platform back? Because when the user wants to save the generated prompt, the Save form needs to POST all three fields: platform, shot, and the generated prompt text. You can't save just the text — the full record needs all three.
.strip() — Remove leading/trailing whitespace from user input before using it. A shot description that's just spaces would produce a useless prompt.
A search bar at the top of the page. When the user types "aerial" and hits Enter, the page shows only prompts that contain "aerial" in the platform name, shot description, or prompt text. It's a GET request — the query goes in the URL: /search?q=aerial. That means it's bookmarkable and the back button works.
@app.route("/search")
def search():
# request.args reads GET parameters (from the URL ?q=...)
query = request.args.get("q", "").lower()
prompts = load_prompts()
if query:
results = [p for p in prompts
if query in p["platform"].lower()
or query in p["shot"].lower()
or query in p["prompt"].lower()]
else:
results = prompts
# TODO: pass counts, all_count, active_filter too
return render_template("index.html",
prompts=results, query=query)
request.args vs request.form — request.form reads POST body data (form submissions). request.args reads GET query parameters (from the URL). Search uses GET, so you use request.args. This is a key distinction.
query and the field values are lowercased before comparison. This way "AERIAL", "Aerial", and "aerial" all match. Call .lower() on both sides.
Before Day 32, draw this diagram (on paper or a notes app). Understanding the data flow before writing code makes implementation faster:
| Browser | → | Flask Route | → | External Call | → | Template |
|---|---|---|---|---|---|---|
| Generate form POST | /generate |
Claude API | index.html with generated= |
|||
| Save Generated POST | /save-generated |
save_prompts() |
redirect to / |
|||
| Search form GET | /search?q=aerial |
load_prompts() |
index.html with query= |
url_for() and can explain why it's better than hardcoded pathsrequest.form (POST) and request.args (GET)prompt-vault/ project is running cleanly before Day 32's build sessionDay 32 is a 75-minute build session. You'll add the /generate route to app.py, wire in the Anthropic SDK, add the generate form and result display to index.html, and add the /save-generated route. By the end, the Prompt Vault generates prompts with AI.